<!--
 * @name: 
 * @Author: JiaYing
 * @LastEditors: JiaYing
 * @Date: 2022-08-30 15:10:19
 * @LastEditTime: 2022-08-30 17:41:36
 * @Description: 
-->
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>defineProperty和Proxy</title>
  </head>
  <body>
    <input value="" />
    <div id="box1"></div>
    <div id="box2"></div>
  </body>
</html>

<script>
  const isObject = function (value) {
    return (
      typeof value == "object" &&
      Array.isArray(value) === false &&
      !(value instanceof Function)
    );
  };
</script>

<script>
  (function () {
    //

    const dep = function () {
      const list = [];

      function listen() {
        [].push.apply(list, arguments);
      }
      function dispatch() {
        list.forEach((fn) => {
          fn.apply(null, arguments);
        });
      }
      return {
        listen,
        dispatch,
      };
    };

    dep.target = null;

    const watch = function (target, attr, cb) {
      const value = target[attr];
      dep.target.listen(cb);
      dep.target = null;
    };

    const observer = function (target, attr, value) {
      if (isObject(value)) value = reactive(value);
      const d = dep();
      Object.defineProperty(target, attr, {
        get() {
          dep.target = d;
          return value;
        },
        set(newValue) {
          if (value === newValue) return;
          if (isObject(newValue)) newValue = reactive(newValue);
          value = newValue;
          d.dispatch(value);
        },
      });
    };

    const reactive = function (obj, target = {}) {
      for (let key in obj) {
        observer(target, key, obj[key]);
      }
      return target;
    };

    const ref = reactive({
      a: 1,
      b: 2,
      c: {
        d: 2,
      },
    });
    console.log(ref);
    document.querySelector("input").addEventListener("input", function (e) {
      ref.b = this.value;
      ref.c.d = this.value;
    });
    const box = document.querySelector("#box1");
    watch(ref.c, "d", function (value) {
      console.log("%c defineProperty b watch => " + value, "color:green");
      box.innerText = value;
    });
  })();
</script>

<script>
  (function () {
    const dep = function () {
      const list = new Map();
      function listen(key, cb) {
        if (list.has(key)) {
          list.get(key).push(cb);
        } else {
          list.set(key, [cb]);
        }
      }
      function dispatch() {
        const key = [].shift.call(arguments, 1);
        if (list.has(key)) {
          list.get(key).forEach((fn) => {
            fn.apply(null, arguments);
          });
        }
      }
      return {
        listen,
        dispatch,
      };
    };

    dep.target = null;

    const watch = function (target, attr, cb) {
      const value = target[attr];
      dep.target.listen(attr, cb);
      dep.target = null;
    };

    const observer = function (target) {
      const d = dep();
      for (let key in target) {
        if (isObject(target[key])) target[key] = reactive(target[key]);
      }
      return new Proxy(target, {
        set(target, key, value) {
          Reflect.set(target, key, value);
          d.dispatch(key, value);
        },
        get(target, key) {
          dep.target = d;
          return Reflect.get(target, key);
        },
      });
    };

    const reactive = function (target) {
      return observer(target);
    };

    const ref = reactive({
      a: 1,
      b: 2,
      c: [1, 2, 3],
      d: {
        e: 1,
      },
    });
    console.log(ref);
    document.querySelector("input").addEventListener("input", function (e) {
      ref.b = this.value;
      ref.d.c = this.value;
    });
    const box = document.querySelector("#box2");
    watch(ref.d, "c", function (value) {
      console.log("%c proxy b watch => " + value, "color:red");
      box.innerText = value;
    });
  })();
</script>
